home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / AppInstall / activation.py < prev    next >
Encoding:
Python Source  |  2009-03-31  |  19.8 KB  |  501 lines

  1. # Copyright (C) 2004-2005 Ross Burton <ross@burtonini.com>
  2. #               2005-2007 Canonical
  3. #
  4. # This program is free software; you can redistribute it and/or modify it under
  5. # the terms of the GNU General Public License as published by the Free Software
  6. # Foundation; either version 2 of the License, or (at your option) any later
  7. # version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU General Public License along with
  15. # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  16. # Place, Suite 330, Boston, MA 02111-1307 USA
  17.  
  18. import sys
  19. import gdbm
  20. import gconf
  21. import errno
  22. import gettext
  23. import os
  24. import os.path
  25.  
  26. from gettext import gettext as _
  27. from optparse import OptionParser
  28.  
  29. class ActivationStyle:
  30.     # Ideally this base class would be in AppInstall or Menu or some
  31.     # such, but that would involve doing `import AppInstall' before we've
  32.     # decided to actually run it, which is too slow for the time between a
  33.     # user clicking on a file and us saying we can't open it.  So we have
  34.     # a full one here and another smaller dummy version in Menu.py too.
  35.  
  36.     def __init__(self):
  37.         self.selectFilter = None
  38.         self.menuFilter = None
  39.         self.isInstallerOnly = False
  40.  
  41.     def isSpecific(self):
  42.         " return True if we are we in a not-normal mode "
  43.         return False
  44.     def menuCacheName(self):
  45.         """ Each activation-mode can have a different cached menu
  46.             This is useful for e.g. codec activation style because
  47.             there are only very few codecs so it makes sense to read
  48.             only the subset of the menu with codec information
  49.         """
  50.         return "menu.p"
  51.     def searchTerms(self):
  52.         return None
  53.     def isApproved(self, component, package):
  54.         return True
  55.     def userApprovedNotify(self):
  56.         pass
  57.     def changesSuccessfulNotify(self):
  58.         pass
  59.     def quitHook(self):
  60.         pass
  61.     def modifyUserInterface(self, app):
  62.         pass
  63.     def getMenuFilter(self):
  64.         return self._menu_filter
  65.     def preRun(self):
  66.         return True
  67.     def autoClose(self):
  68.         " auto close on successful install/remove "
  69.         return False
  70.  
  71. class SearchActivationStyle(ActivationStyle):
  72.     def __init__(self, dictname, options):
  73.         self._dn = dictname
  74.         self._cachedir = options.cachedir
  75.         self._userapproved = False
  76.         self._changessuccessful = False
  77.         self.menuFilter = None
  78.  
  79.         # read the packages whitelist (from the files)
  80.         dict = {}
  81.         for d in (options.datadir, '/etc/gnome-app-install'):
  82.             try: f = open(d+'/packages-whitelist')
  83.             except IOError, e:
  84.                 if e.errno == errno.ENOENT: continue
  85.                 raise
  86.             for l in f:
  87.                 v = l.strip()
  88.                 if v.startswith('#'): continue
  89.                 dict[v] = True
  90.         self._wl_packages = dict
  91.  
  92.         # read the component whitelist (from gconf)
  93.         try:
  94.             client = gconf.client_get_default()
  95.             l = client.get_list("/apps/gnome-app-install"+
  96.                                 "/mime-whitelist-components",
  97.                                 gconf.VALUE_STRING)
  98.         except gobject.GError, e:
  99.             # default to "main" if gconf is not available
  100.             print "Error in gconf: %s" % e
  101.             l = ["main"]
  102.         dict = {}
  103.         for v in l: dict[v] = True
  104.         self._wl_components = dict
  105.  
  106.         db_name = self._cachedir+'/gai-'+self._dn+'-map.gdbm'
  107.         self._db = gdbm.open(db_name, 'rfu')
  108.  
  109.     def menuCacheName(self):
  110.         return "mime_menu.p"
  111.  
  112.     def lookup(self,string):
  113.         # look up our entry and bomb if not found
  114.         try: value = self._db[string]
  115.         except KeyError: value = ''
  116.  
  117.         unapproved = False
  118.         for e in value.split():
  119.             (component,package) = e.split('/',1)
  120.             if self.isApproved(component,package): return (True,True,None,None)
  121.             unapproved = True
  122.  
  123.         if unapproved: (abbrev,msg) = (
  124.             _("no suitable application"),
  125.             _("No application suitable for automatic installation is"
  126.               " available for handling this kind of file."))
  127.         else: (abbrev,msg) = (
  128.             _("no application found"),
  129.             _("No application is known for this kind of file."))
  130.  
  131.         return (False, unapproved, abbrev, msg)
  132.  
  133.     def isApproved(self, component, package):
  134.         return (self._wl_components.has_key(component) or
  135.             self._wl_packages.has_key(package))
  136.     def isSpecific(self): return True
  137.     def userApprovedNotify(self): self._userapproved = True
  138.     def changesSuccessfulNotify(self): self._changessuccessful = True
  139.     def modifyUserInterface(self, app):
  140.         app.scrolledwindow_left.hide()
  141.         app.label_progress.set_markup("<big><b>%s</b></big>\n\n%s" %
  142.                                       (_("Searching for appropriate "
  143.                                          "applications"),
  144.                                        _("Please wait. This might take a "
  145.                                          "minute or two.")))
  146.  
  147. class CodecSearchActivationStyle(SearchActivationStyle):
  148.     def __init__(self, options, args):
  149.        SearchActivationStyle.__init__(self, 'codec', options)
  150.        self._codecs = []
  151.        self._args = args
  152.        self.options = options
  153.        # Points to the SHOW_ALL filter, importing Menu would take too much time
  154.        self.menuFilter = 0
  155.        self.isInstallerOnly = True
  156.  
  157.     def _parseArgs(self):
  158.         " helper that tries to decode the commandline"
  159.         for arg in self._args:
  160.             # we get a string like this:
  161.             #gstreamer.net|0.10|totem|DivX MPEG-4 Version 5 decoder|decoder-video/x-divx, divxversion=(int)5 (DivX MPEG-4 Version 5 decoder)
  162.             try:
  163.                 (origin,version,app,descr,search_token) = arg.split("|")
  164.             except ValueError, e:
  165.                 sys.stderr.write("invalid commandline '%s' (%s)\n" % (arg, e))
  166.                 return False
  167.             self.addSearchTerm("%s:%s" % (version, search_token))
  168.         return True
  169.     
  170.     def preRun(self):
  171.         if self._parseArgs():
  172.             if not askConfirmation(_("Search for suitable codec?"),
  173.                             _("The required software to play this "
  174.                               "file is not installed. You need to install "
  175.                               "suitable codecs to play "
  176.                               "media files. Do you want to search for a codec "
  177.                               "that supports the selected file?\n\n"
  178.                               "The search will also include software which is not "
  179.                               "officially supported."),
  180.                             self.options.transient_for):
  181.                 sys.exit(4)
  182.         else:
  183.             import gtk
  184.             abbrev = _("Invalid commandline")
  185.             msg = _("'%s' does not understand the commandline argument '%s'" % (sys.argv[0], self._args))
  186.             dlg = gtk.MessageDialog(None,
  187.                                     gtk.DIALOG_MODAL,
  188.                                     gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
  189.                                     abbrev)
  190.             dlg.format_secondary_text(msg)
  191.             dlg.run()
  192.             dlg.destroy()
  193.             sys.exit(1)
  194.         return True
  195.  
  196.     def modifyUserInterface(self, app):
  197.         import gtk
  198.         import distros
  199.  
  200.         SearchActivationStyle.modifyUserInterface(self, app)
  201.  
  202.         app.textview_description.show_message("", 
  203.           _("Some countries allow patents on software, and freely "
  204.             "redistributable software like Ubuntu cannot pay for patent "
  205.             "licenses. If you are in "
  206.             "one of these jurisdictions, you can buy licensed media playback "
  207.             "plug-ins from the Canonical Store. Otherwise, select a free "
  208.             "plug-in above to install it."))
  209.         app.button_help.hide()
  210.         app.treeview_packages.set_headers_visible(False)
  211.         app.treeview_packages.column_app_popcon.set_visible(False)
  212.  
  213.         #FIXME: second message needs some love
  214.         app.label_progress.set_markup("<big><b>%s</b></big>\n\n%s" %
  215.                                       (_("Searching for appropriate "
  216.                                          "codecs"),
  217.                                        _("Please wait. This might take a "
  218.                                          "minute or two.")))
  219.         app.hbox_search_show.hide()
  220.         app.button_ok.set_label(_("_Install"))
  221.         app.window_main.set_title(_("Install Media Plug-ins"))
  222.         app.window_main.set_property("default_width", 500)
  223.         app.window_main.set_property("default_height", 400)
  224.         col = app.treeview_packages.get_column(1)
  225.         col.set_title(_("Codec"))
  226.         model = app.treeview_packages.get_model()
  227.         model.set_sort_column_id(2, gtk.SORT_DESCENDING)
  228.         # add codec link
  229.         distro = distros.get_distro()
  230.         (label, url) = distro.get_codec_information_link()
  231.         if (label is not None) and (url is not None):
  232.             button = gtk.Button(label)
  233.             button.uri = url
  234.             button.connect("clicked", self.uri_clicked)
  235.             button.show()
  236.             app.hbox_help.pack_end(button)
  237.  
  238.     def uri_clicked(self, button):
  239.         import subprocess
  240.         for opener in ["gnome-open", "sensible-browser", 
  241.                        "xdg-open", "x-www-browser"]:
  242.             for d in os.environ["PATH"].split(":"):
  243.                 if os.path.exists(os.path.join(d,opener)):
  244.                     print "found ", opener
  245.                     subprocess.call([opener,button.uri])
  246.                     return
  247.  
  248.     def menuCacheName(self):
  249.         return "codec_menu.p"
  250.  
  251.     def addSearchTerm(self, string):
  252.         #print "addSearchTerm: ", string
  253.         # we split the string here for the gstreamer caps support
  254.         # we get something like:
  255.         #  'decoder-video/x-indeo, indeoversion=(int)3'
  256.         # and we will only compare the first bit as the later requires
  257.         # more python-gst support and that is slow. but we want to
  258.         # give the user fast results (even if we sometimes come up
  259.         # with a window saying "no applications found"
  260.         string = string.replace(", ",",")
  261.         #Skip the description from the search token
  262.         string = string.split(" ")[0]
  263.         self._codecs.append(string)
  264.         (ok,unapproved,abbrev,msg) = self.lookup(string.split(",")[0])
  265.         if ok: return
  266.         print >>sys.stderr, abbrev
  267.         #sys.exit(9 - unapproved)
  268.         sys.exit(1)
  269.         
  270.     def searchTerms(self): return self._codecs
  271.     def selectFilter(self, menu): return menu._codecMatch
  272.  
  273.     def quitHook(self):
  274.        if not self._userapproved:
  275.                #print >>sys.stderr, _("additional codec installation declined")
  276.                #sys.exit(3)
  277.                sys.exit(4)
  278.        if not self._changessuccessful:
  279.                #print >>sys.stderr, _("additional codec installation failed")
  280.                sys.exit(2)
  281.  
  282.     def autoClose(self):
  283.         " auto close the app on successful codec installs "
  284.         return True
  285.  
  286. class MimeSearchActivationStyle(SearchActivationStyle):
  287.     def __init__(self, options, uri, duri):
  288.         SearchActivationStyle.__init__(self, 'mime', options)
  289.         self._uri = uri
  290.         self._duri = duri
  291.         self._string = options.mime_type
  292.         self.isInstallerOnly = True
  293.  
  294.     def preRun(self):
  295.         (ok,unapproved,abbrev,msg) = self.lookup(self._string)
  296.         if ok: return True
  297.  
  298.         if self._uri:
  299.             import gtk
  300.             import AppInstall
  301.             import os.path
  302.  
  303.             #TRANSLATORS: %s represents a file path
  304.             header = _("\"%s\" cannot be opened") % os.path.basename(self._duri)
  305.             dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL,
  306.                 gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, header)
  307.             dlg.format_secondary_text(msg)
  308.             dlg.set_title(header)
  309.             dlg.run()
  310.             dlg.destroy()
  311.             print >>sys.stderr, abbrev
  312.             sys.exit(6)
  313.         else:
  314.             print >>sys.stderr, "not offering packages for %s" % self._string
  315.             if unapproved:
  316.                 print >>sys.stderr, "only unapproved: %s" % self._string
  317.                 sys.exit(5)
  318.             else:
  319.                 print >>sys.stderr, "no entry in mime map"
  320.                 sys.exit(4)
  321.  
  322.     def searchTerms(self): return [self._string]
  323.     def selectFilter(self, menu): return menu._mimeMatch
  324.  
  325.     def quitHook(self):
  326.         if self._uri and self._changessuccessful:
  327.             import gnomevfs
  328.             gnomevfs.url_show(self._uri)
  329.  
  330.     def modifyUserInterface(self, app):
  331.         import xdg.Mime
  332.         import os.path
  333.         import gtk
  334.         mime = xdg.Mime.lookup(self._string)
  335.         SearchActivationStyle.modifyUserInterface(self, app)
  336.         app.label_progress.set_markup("<big><b>%s</b></big>\n\n%s" %
  337.                                       (_("Searching for appropriate "
  338.                                          "applications"),
  339.                                        _("A list of applications that can "
  340.                                          "handle documents of the type '%s' "
  341.                                          "will be created") % mime.get_comment()))
  342.         app.button_ok.set_label(_("_Install"))
  343.         if self._uri:
  344.             #TRANSLATORS: %s represents a file path
  345.             app.window_main.set_title(_("Install applications to open \"%s\"")\
  346.                                       % os.path.basename(self._duri))
  347.         else:
  348.             app.window_main.set_title(_("Install applications"))
  349.         app.window_main.set_property("default_width", 500)
  350.         model = app.treeview_packages.get_model()
  351.         model.set_sort_column_id(2, gtk.SORT_DESCENDING)
  352.  
  353. def askConfirmation(summary, msg, transient_for=None):
  354.     import gtk
  355.     dlg = gtk.MessageDialog(None, gtk.DIALOG_MODAL,
  356.                             gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL,
  357.                             summary)
  358.     dlg.format_secondary_text(msg)
  359.     btn = dlg.add_button(_("_Search"), gtk.RESPONSE_YES)
  360.     btn.grab_focus()
  361.     if not transient_for:
  362.         dlg.set_title(summary)
  363.     if transient_for:
  364.         parent = gtk.gdk.window_foreign_new(transient_for)
  365.         if parent:
  366.             dlg.realize()
  367.             dlg.window.set_transient_for(parent)
  368.     res = dlg.run()
  369.     dlg.destroy()
  370.     while gtk.events_pending():
  371.         gtk.main_iteration()
  372.     if res == gtk.RESPONSE_YES:
  373.         return True
  374.     return False
  375.  
  376. class XULExtensionsActivationStyle(MimeSearchActivationStyle):
  377.     def __init__(self, options):
  378.         MimeSearchActivationStyle.__init__(self, options, uri=None, duri=None)
  379.         self._string = "application/x-debian-xul-extension-%s" % \
  380.                        options.xul_extensions.lower()
  381.  
  382.     # all extensions are in universe but are maintained security wise by
  383.     # the firefox team (see #267392)
  384.     def isApproved(self, component, package):
  385.         return True
  386.  
  387.     def quitHook(self):
  388.         if self._changessuccessful:
  389.             print >>sys.stderr, "changed extensions"
  390.             sys.exit(0)
  391.         else:
  392.             print >>sys.stderr, "did not change anything"
  393.             sys.exit(1)
  394.  
  395.     def modifyUserInterface(self, app):
  396.         SearchActivationStyle.modifyUserInterface(self, app)
  397.         app.label_progress.set_markup("<big><b>%s</b></big>\n\n%s" %
  398.                                       (_("Searching for extensions"),
  399.                                        _("Extensions allow you to add new "
  400.                                          "features to your application.")))
  401.         app.window_main.set_title(_("Install/Remove Extensions"))
  402.         col = app.treeview_packages.get_column(1)
  403.         col.set_title(_("Extension"))
  404.  
  405.     def preRun(self):
  406.         return True
  407.  
  408. def main():
  409.     app="gnome-app-install"
  410.     gettext.textdomain(app)
  411.     gettext.bindtextdomain(app)
  412.  
  413.     parser = OptionParser()
  414.     parser.add_option("", "--mime-type",
  415.                       default=None,
  416.                       action="store", type="string", dest="mime_type",
  417.                       help="Show only applications that handle the given "
  418.                            "file type")
  419.     parser.add_option("", "--transient-for",
  420.                       default=None,
  421.                       action="store", type="int", dest="transient_for",
  422.                       help="Start as a child window of the given window (Only "
  423.                            "needed by developers")
  424.     parser.add_option("", "--data-dir",
  425.                       default="/usr/share/gnome-app-install",
  426.                       action="store", type="string", dest="datadir",
  427.                       help="Load data from the given directory (Only needed "
  428.                            "by developers)")
  429.     parser.add_option("", "--desktopdir",
  430.                       default="/usr/share/app-install",
  431.                       action="store", type="string", dest="desktopdir",
  432.                       help="Read the desktop files from the given directory "
  433.                            "(Only needed by developers)")
  434.     parser.add_option("", "--cachedir",
  435.                       default="/var/cache/app-install",
  436.                       action="store", type="string", dest="cachedir",
  437.                       help="Use the given directory for the cache (Only "
  438.                            "needed by developers)")
  439.     parser.add_option("", "--xul-extensions",
  440.                       default=None,
  441.                       action="store", type="string", dest="xul_extensions",
  442.                       help="Start as installer for XUL extensions")
  443.     parser.add_option("", "--addon-cd",
  444.                       default=None,
  445.                       action="store", type="string", dest="addon_cd",
  446.                       help="Start as installer for an addon cd")
  447.     parser.add_option("", "--selftest", action="store_true",
  448.                       dest="selftest", help="Perform self tests (Only needed "
  449.                                             "by developers)")
  450.     parser.add_option("", "--profile", action="store", type="string",
  451.                       dest="profile", default=None,
  452.                       help="Store profiling data in the given file "
  453.                            "(Only needed by developers)")
  454.     parser.add_option("", "--test-mode", action="store_true", dest="test_mode",
  455.                       help="Run in a sepcial test mode"
  456.                            "(Only needed by developers)")
  457.     (options, args) = parser.parse_args()
  458.  
  459.     if options.selftest:
  460.         from AppInstall import AppInstall
  461.         app = AppInstall(options, ActivationStyle())
  462.         while True:
  463.             model = app.treeview_packages.get_model()
  464.             it = model.get_iter_root()
  465.             (name, item, popcon) = model[it]
  466.             app.applyChanges([item], [])
  467.  
  468.     # activation style
  469.     if sys.argv[0].split("/")[-1] == "gstreamer-codec-install":
  470.         style = CodecSearchActivationStyle(options, args)
  471.     elif options.mime_type:
  472.         #FIXME: What is an uri, duri?
  473.         uri = None
  474.         duri = None
  475.         if len(args) > 1:
  476.             uri = args[0]
  477.             duri = args[1]
  478.         elif len(args) > 0:
  479.             uri = args[0]
  480.             duri = uri
  481.         style = MimeSearchActivationStyle(options, uri, duri)
  482.     elif options.xul_extensions:
  483.         style = XULExtensionsActivationStyle(options)
  484.     else:
  485.         style = ActivationStyle()
  486.  
  487.     if style.preRun():
  488.         # We have already bombed out if the quick test fails.  We do this
  489.         #  import only now so that quick tests are really quick.
  490.         from AppInstall import AppInstall
  491.         #FIXME: could be easily made simpler, talk with aptoncd author 
  492.         #       before any api changes
  493.         app = AppInstall(options, style)
  494.         if options.profile:
  495.             import hotshot
  496.             prof = hotshot.Profile(options.profile)
  497.             print prof.runcall(app.run)
  498.             prof.close()
  499.         else:
  500.             app.run()
  501.